Skip to content

fix(dnd): update useDescription to prevent unnecessary re-renders#9738

Closed
reidbarber wants to merge 6 commits intomainfrom
dnd-perf-useDynamicDescription
Closed

fix(dnd): update useDescription to prevent unnecessary re-renders#9738
reidbarber wants to merge 6 commits intomainfrom
dnd-perf-useDynamicDescription

Conversation

@reidbarber
Copy link
Member

@reidbarber reidbarber commented Mar 4, 2026

Closes #2504

Adds a new opt-in descriptionKey option to the hook that gets used by useDrag and useVirtualDrop.

It solves the following issue with useDescription:

  • You render a collection of 100 draggable items on the page.
  • You switch between drag modalities (e.g. go from mouse to keyboard).
  • The accessible description for the drag buttons updates from "Click to start dragging." to "Press Enter to start dragging."
  • A new node was created for that change, and the old one was removed.
  • All 100 drag buttons would re-render in order to reference that new node

Our new option changes the text content of the existing description node, so the 100 drag buttons do not need to re-render in this case. We need to key these, so that multiple consumers can reliably share the same node.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

In the RAC DnD Table story, open dev tools to inspect the element.

Alternative between clicking and tabbing to switch drag modalities, and observe:

  • The description node text updates, but it's id stays the same
  • All drag button point to that description node, and don't re-render when it updates.

🧢 Your Project:

@github-actions github-actions bot added the RAC label Mar 4, 2026
@reidbarber reidbarber changed the title perf(dnd): add useDynamicDescription to prevent unnecessary re-renders fix(dnd): add useDynamicDescription to prevent unnecessary re-renders Mar 4, 2026
function getOrCreateDynamicDescriptionNode(descriptionKey: string) {
let desc = dynamicDescriptionNodes.get(descriptionKey);
if (!desc) {
let id = `react-aria-description-${descriptionId++}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the case that multiple copies are loaded, this could create conflicting ids, better to use id generation like crypto.randomUUID

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or put the id generation into the hooks and make use of useId

}, [description]);

useLayoutEffect(() => {
return () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you move this up to be in the first useLayoutEffect, then you can de-duplicate the refCount removal, always handle it in the cleanup of that effect
It'll make it more readable as well because creation and cleanup associated will be right next to each other

@reidbarber reidbarber changed the title fix(dnd): add useDynamicDescription to prevent unnecessary re-renders fix(dnd): update useDescription to prevent unnecessary re-renders Mar 17, 2026
@rspbot
Copy link

rspbot commented Mar 17, 2026

@rspbot
Copy link

rspbot commented Mar 17, 2026

@rspbot
Copy link

rspbot commented Mar 17, 2026

## API Changes

@react-aria/utils

/@react-aria/utils:useDescription

 useDescription {
   description?: string
+  descriptionKey?: string
   returnVal: undefined
 }

}
};

const DESCRIPTION_KEYS = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If half the app is in ja-JP and the other half is in en-US, these keys will conflict and some english descriptions will end up in jp or vice versa?
Probably have to use the translated string itself as the key

}, [description, descriptionKey, isDynamic]);

desc.refCount++;
useLayoutEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this can be moved up into the previous layout effect, negating the need to call cleanup on line 82

* a stable node is shared by key and its text content updates in place as the
* description changes.
*/
export function useDescription(description?: string, descriptionKey?: string): AriaLabelingProps {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could do this without a special key - basically try to keep the id the same over time. When the description changes, if the ref count is zero, reuse the same id for the new description.

@devongovett
Copy link
Member

hmm actually, what do you think about using aria-description instead of aria-describedby? Then we don't need to create these extra nodes at all. Looks like it has pretty good browser support now but we'd need to test all the screen readers. https://caniuse.com/mdn-api_element_ariadescription

@reidbarber
Copy link
Member Author

@devongovett that would definitely be better. I'll close this and try that out in a separate PR soon.

@reidbarber reidbarber closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useDrag re-renders from useDescription

4 participants